package pers.cz.tools;

import pers.cz.config.Config;

import javax.tools.*;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 动态编译工具
 * @program: PostGirl-panent
 * @description: DynamicCompilation
 * @author: Cheng Zhi
 * @create: 2021-11-19 14:37
 **/
public class DynamicCompilationUtil {

    private String jars = "";

    /**
     * 加载class文件到虚拟机中并执行
     * @param filePath class文件保存路径
     * @param methodName 要执行的方法名
     * @param className 加载的class类全名
     * @throws MalformedURLException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public Map<Type,Object> loadAndRunClass(String filePath, String methodName, String className) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Map map = new HashMap();
        File file = new File(filePath);
        URL[] urls = {file.toURI().toURL()};
        URLClassLoader classLoader = new URLClassLoader(urls, this.getClass().getClassLoader());
        // 获取class
        Class<?> scriptClass = classLoader.loadClass(className);
        // 实例化
        Object newInstance = scriptClass.newInstance();
        // 执行方法
        Object invoke = scriptClass.getMethod(methodName).invoke(newInstance);
        // 获取返回类型
        Type genericReturnType = scriptClass.getMethod(methodName).getGenericReturnType();

        map.put(genericReturnType,invoke);

        return map;
    }

    /**
     * 加载class文件到虚拟机中并执行用于调试
     * @param filePath class文件保存路径
     * @param methodName 要执行的方法名
     * @param className 加载的class类全名
     * @throws MalformedURLException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public Object loadAndDebugClass(String filePath, String methodName, String className) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Map map = new HashMap();
        File file = new File(filePath);
        URL[] urls = {file.toURI().toURL()};
        URLClassLoader classLoader = new URLClassLoader(urls, this.getClass().getClassLoader());
        // 获取class
        Class<?> scriptClass = classLoader.loadClass(className);
        // 实例化
        Object newInstance = scriptClass.newInstance();
        // 执行方法
        Object invoke = scriptClass.getMethod(methodName).invoke(newInstance);
        // 获取返回类型
        Type genericReturnType = scriptClass.getMethod(methodName).getGenericReturnType();

        //map.put(genericReturnType,invoke);

        return invoke;
    }

    /**
     * 编译java文件
     * @param javaContent 要编译的内容
     * @param jarPath 依赖jar包路径
     * @param saveClassPath 编译后的.class文件保存路径
     */
    public String compileJava(String javaContent, String jarPath, String saveClassPath, String className) {

        try {
            // 创建java编译器实例
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            // 创建对象用于获取编译输出信息
            DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
            // 获得JavaFileManager文件管理器对象，用于管理需要编译的文件
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null);
            // 生成一个JavaFileObject对象，表示需要编译的源文件
            GenericJavaFileObject fileObject = new GenericJavaFileObject(className, javaContent);
            // 获取该工程下所有的jar文件。
            String importPagePathLogin = getJarFiles(jarPath);

            // 编译选项，在编译java文件时，编译程序会自动的去寻找java文件引用的其他的java源文件或者class。
            // -sourcepath选项就是定义java源文件的查找目录， -classpath选项就是定义class文件的查找目录，-d就是编译文件的输出目录。
            Map<String, String> cacheMap = Config.getConfig().getsysConfig();
            String isSave = cacheMap.get("isSave");
            Iterable<String> options = null;
            if ("true".equals(isSave)) {
                options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);
            } else {
                options =  Arrays.asList( "-classpath", importPagePathLogin);
            }
            //Iterable<String> options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);
            //Iterable<String> options = Arrays.asList( "-classpath", importPagePathLogin);
            // 将java文件转化为list
            Iterable<? extends JavaFileObject> fileObjects = Collections.singletonList(fileObject);

            // 获取编译任务（1、第一个参数为文件输出，这里我们可以不指定，我们采用javac命令的-d参数来指定class文件的生成目录
            // 2、第二个参数为文件管理器实例
            // 3、DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时，存放编译错误信息
            // 4、第四个参数为编译命令选项，就是javac命令的可选项，这里我们主要使用了-d和-sourcepath这两个选项
            // 5、第五个参数为类名称
            // 6、第六个参数为上面提到的编译单元，就是我们需要编译的java源文件）
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjects);
            // 编译
            Boolean result = task.call();

            if (result) {
                System.out.println("compilation success!");
                return "success";
            } else {
                System.out.println("compilation fail!!!");
                // 编译失败，打印错误信息
                StringBuilder errorInfo = new StringBuilder();
                for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) {
                    errorInfo.append("编译错误。 Code:").append(diagnostic.getCode())
                            .append("\r\n").append("Kind:").append(diagnostic.getKind())
                            .append("\r\n").append("StartPosition:").append(diagnostic.getStartPosition())
                            .append("\r\n").append("EndPosition:").append(diagnostic.getEndPosition())
                            .append("\r\n").append("Position:").append(diagnostic.getPosition())
                            .append("\r\n").append("Source:").append(diagnostic.getSource())
                            .append("\r\n").append("Message:").append(diagnostic.getMessage(null))
                            .append("\r\n").append("ColumnNumber:").append(diagnostic.getColumnNumber())
                            .append("\r\n").append("LineNumber:").append(diagnostic.getLineNumber());
                }
                System.out.println("错误信息如下：" + errorInfo);
                return errorInfo.toString();
            }

        } catch(Exception e) {

            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查找该目录下的所有的jar文件
     * @param jarPath
     * @return
     * @throws Exception
     */
    private String getJarFiles(String jarPath) throws Exception {
        Map<String, String> cacheMap = Config.getConfig().getsysConfig();
        String isWindows = cacheMap.get("isWindows");
        File sourceFile = new File(jarPath);
        // String jars="";
        if (sourceFile.exists()) {// 文件或者目录必须存在
            if (sourceFile.isDirectory()) {// 若file对象为目录
                // 得到该目录下以.java结尾的文件或者目录
                File[] childrenFiles = sourceFile.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        if (pathname.isDirectory()) {
                            return true;
                        } else {
                            String name = pathname.getName();
                            if (name.endsWith(".jar") ? true : false) {
                                if ("true".equals(isWindows)) {
                                    jars = jars + pathname.getPath() + ";";
                                } else {
                                    jars = jars + pathname.getPath() + ":";
                                }

                                return true;
                            }
                            return false;
                        }
                    }
                });
            }
        }
        return jars;
    }

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, MalformedURLException, ClassNotFoundException {

        // 脚本内容
        //String javaScript = "package pers.cz.postgirl.test;import org.apache.ibatis.session.SqlSession;import java.util.List;import java.io.IOException;import java.util.Date;public class MyJavaClass {public String hello() throws IOException {int a = 1;int b= 2;int c= 3;System.out.println(a + b + c);System.out.println(Math.random());Date date = new Date();return \"czzzz\";}}";
        String javaScript2 = "package pers.cz.postgirl.test;import org.apache.ibatis.session.SqlSession;import java.io.IOException;import java.util.Date;public class MyJavaClass {public void hello() throws IOException {int a = 1;int b= 2;int c= 3;System.out.println(a + b + c);System.out.println(Math.random());Date date = new Date();}}";
        String javaScript = "";


        // 脚本依赖jar包的存放路径
        //String jarPath = "D:\\Java\\Project\\PostGirl-panent\\PostGirl-admin\\target\\PostGirl-admin-1.0-SNAPSHOT\\WEB-INF\\lib";
        String jarPath = "C:\\Users\\cz\\Desktop\\ams业务代码\\lib";
        // java脚本的全类名
        String className = "com.ailk.openbilling.ams.business.outInterface.QueryBillExtNewBusiness";
        // 要执行的方法名
        String methodName = "hello";
        // 编译后的文件保存路径
        String saveCalssPath = "C:\\Users\\cz\\Desktop\\hotswap";

        DynamicCompilationUtil dynamicCompilationUtil = new DynamicCompilationUtil();
        // 编译文件
        String[] split = className.split("\\.");
        //String name = split[split.length-1];
        String name = "QueryBillExtNewBusiness";

        dynamicCompilationUtil.compileJava(javaScript, jarPath, saveCalssPath, name);
        // 反射执行
/*        Map<Type, Object> typeObjectMap = dynamicCompilationUtil.loadAndRunClass(saveCalssPath, methodName, className);
        TypeFactory typeFactory = new TypeFactory();
        for (Type type : typeObjectMap.keySet()) {
            ITypeJudge typeJudge = typeFactory.getTypeJudge(type.getTypeName());
            // 拿到类型
            System.out.println(typeJudge.handleJudge(typeObjectMap.get(type)));

        }*/

    }
}
